﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;

//We need FullTrust added to web.config for web application to do this :(
//[assembly: System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.RequestMinimum, Name = "Execution")]
namespace uk.ac.soton.ses.DAL
{
    /// <summary>
    /// Generic class that searches for plug-ins of the type specified in the appropriate folder
    /// </summary>
    /// <typeparam name="T">The type of plug-in to search for. The type also affects the folder that is searched.</typeparam>
    public class HDCPlugins<T> where T : class,IHDCPlugin
    {
        private List<T> _plugins = null;
        private string _dllfolder;

        /// <summary>
        /// The list of plug-ins that have been found. This is populated lazily and the results cached, so the first time this property is accessed will take longer than future calls.
        /// </summary>
        public List<T> Plugins
        {
            get
            {
                //We will do this lazily, so we only populate the plug-ins when they are requested
                if (this._plugins == null)
                {
                    if (string.IsNullOrEmpty(this._dllfolder) || !Directory.Exists(this._dllfolder))
                    {
                        this._plugins = null;
                        throw new DirectoryNotFoundException("Plugin directory not found");
                    }
                    else
                    {
                        this._plugins = HDCPlugins<T>.FindDataAccessAssemblies(this._dllfolder);
                    }
                }
                return this._plugins;
            }
            set { this._plugins = value; }
        }

        /// <summary>
        /// Default constructor which sets the folder that will be 
        /// </summary>
        public HDCPlugins()
        {
            //We will load from a different directory depending on the path
            if(typeof(T) == typeof(IHDCExperimentPlugin))
            {
                this._dllfolder = HDCConfigurationOptionsProperties.Default.ExperimentPluginFolderPath;
            }
            else if (typeof(T) == typeof(IHDCExperimentDataFilePlugin))
            {
                this._dllfolder = HDCConfigurationOptionsProperties.Default.ExperimentDataFilePluginFolderPath;
            }
            else if (typeof(T) == typeof(IHDCMetadataSyncServicePlugin))
            {
                this._dllfolder = HDCConfigurationOptionsProperties.Default.MetadataSyncServicePluginFolderPath;
            }
            else
            {
                this._dllfolder = HDCConfigurationOptionsProperties.Default.DefaultPluginFolderPath;
            }
        }

        public HDCPlugins(string dllFolder)
        {
            this._dllfolder = dllFolder;
        }
    
        public static List<T> FindDataAccessAssemblies(string dllfolder) 
        {
            List<T> pluginList = new List<T>();
            //We only continue if the folder does not exist. No need to throw an exception as this means no folder means no plugins have been added.
            if (Directory.Exists(dllfolder))
            {
                //Find files that have a .dll extension
                string[] dlls = Directory.GetFiles(dllfolder, "*.dll", SearchOption.TopDirectoryOnly);
                foreach (string dll in dlls)
                {
                    Assembly assem = null;
                    try
                    {
                        //We will do it this way, rather than LoadFile(dll) so we don't lock the file
                        assem = Assembly.Load(File.ReadAllBytes(dll));
                        pluginList.AddRange(HDCPlugins<T>.GetPluginsFromAssembly(assem));
                    }
                    catch (BadImageFormatException)
                    {
                        //This assembly was bad, so we'll move onto the next one
                        continue;
                    }
                }
            }
            return pluginList;
        }

        public static List<T> GetPluginsFromAssembly(Assembly assem)
        {
            List<T> pluginList = new List<T>();
            //Find all classes that implement the plug in interface
            string pluginInterface = typeof(T).Name;
            try
            {
                Type[] types = assem.GetTypes();
                foreach (Type type in types)
                {
                    if (type.GetInterface(pluginInterface) != null)
                    {
                        object paramObj = assem.CreateInstance(type.FullName);
                        T importPlugin = paramObj as T;
                        //Only add the plugin if we could cast it to type <T>
                        if (importPlugin != null)
                        {
                            pluginList.Add(importPlugin);
                        }
                    }
                }
            }
            //If the types can't be loaded, we can just return the list as it is (probably empty)
            catch (ReflectionTypeLoadException)
            {
            }

            return pluginList;
        }
    }
}
